#include "mod-ipv6.hpp"


Record * IPv6Decoder::decode (Packet &pkt, Record &prev)
{
  IPv6Hdr * hdr = (IPv6Hdr*) pkt.getCur ();
  Record &r = this->rec;
  IPv6Session * ssn;
  void * ssnKey;
  Node * src, * dst;

  if (! r.lock ())
    return NULL;

  if (NULL == hdr || ! pkt.moveCur (sizeof (IPv6Hdr)))
    return NULL;
  
  r.setData (DEC_IPV6, hdr, sizeof (IPv6Hdr));
  ssnKey = this->makeSsnKey (&hdr->ipSrc, &hdr->ipDst, ADDR_LEN);

  if (NULL == (ssn = dynamic_cast<IPv6Session*>
	       (this->ssnTable.lookup (ssnKey, ADDR_LEN * 2))))
    {
      ssn = new IPv6Session ();
      ssn->joinTable (this->ssnTable, ssnKey, ADDR_LEN * 2);

      if (NULL == (src = this->nodeTable.lookup (&hdr->ipSrc, ADDR_LEN)))
	{
	  src = new Node ();
	  src->joinTable (this->nodeTable, 
			  static_cast<void*>(&hdr->ipSrc), ADDR_LEN);
	}

      if (NULL == (dst = this->nodeTable.lookup (&hdr->ipDst, ADDR_LEN)))
	{
	  dst = new Node ();
	  dst->joinTable (this->nodeTable, 
			  static_cast<void*>(&hdr->ipDst), ADDR_LEN);
	}

      ssn->setNode (src, dst, hdr->ipSrc, hdr->ipDst);
    }
  else
    {
      src = ssn->getNode (hdr->ipSrc);
      dst = ssn->getNode (hdr->ipDst);      
      src->setTimer (this->getTimer ());
      dst->setTimer (this->getTimer ());
    }
  

  ssn->setTimeout (this->getTimer (), SSN_TIMEOUT);
  r.setNode (src, dst);
  r.setSession (ssn);

  // ToDo:
  // to be implemented IPv6 Next Header handler

  switch (hdr->ipNH)
    {
    case IPPROTO_TCP: 
      r.setNextDecoder (DEC_TCP); 
      r.setSessionRootPtr (&ssn->root[0]);
      break;

    case IPPROTO_UDP: 
      r.setNextDecoder (DEC_UDP);
      r.setSessionRootPtr (&ssn->root[1]);
      break;

    case IPPROTO_ICMP6:
      r.setNextDecoder (DEC_ICMP6);
      r.setSessionRootPtr (&ssn->root[2]);      
      break;

    default:
      /* ignore */
      break;
    }

  return &(this->rec);
}


NDecFilter * IPv6Decoder::makeFilter (string &name)
{
  IPv6Filter * f = NULL;

  if (name == "ipv6.src_addr")
    f = new IPv6Filter (IPv6Filter::FT_SRC_ADDR);
  else if (name == "ipv6.dst_addr")
    f = new IPv6Filter (IPv6Filter::FT_DST_ADDR);

  return f;
}


void IPv6Session::setNode 
(Node * _LNode, Node * _RNode, u_int32_t *_LKey, u_int32_t *_RKey)
{
  this->Session::setNode (_LNode, _RNode);
  memcpy (this->LKey, _LKey, IPv6Decoder::ADDR_LEN);
  memcpy (this->RKey, _RKey, IPv6Decoder::ADDR_LEN);
}

Node * IPv6Session::getNode (u_int32_t *key)
{
  if (memcmp (this->LKey, key, IPv6Decoder::ADDR_LEN) == 0)
    return this->getLeftNode ();
  else if (memcmp (this->RKey, key, IPv6Decoder::ADDR_LEN) == 0)
    return this->getRightNode ();
  else
    assert (0);
}

//=============================================================================
bool IPv6Filter::setup (string &arg)
{
  try 
    {
      memset (this->mask, ~0, sizeof (this->mask));
      string strAddr = arg, strMask;

      if (string::npos != arg.find ("/"))
	{
	  int idx = arg.find ("/");
	  strAddr = arg.substr (0, idx);
	  strMask = arg.substr (idx);
	  
	  char * e;
	  int i;
	  int prefix = strtol (strMask.c_str (), &e, 0);
	  if (prefix <= 0 || 128 <= prefix)
	    throw string ("Invalid prefix length '" + arg + "'");

	  for (i = 0; i < prefix / 32; i++)
	    this->mask [3 - i] = 0;

	  if (prefix % 32 > 0)
	    {
	      this->mask [2 - i] = this->mask [2 - i] << prefix % 32;
	    }
	}

      if (1 != inet_pton (AF_INET6, strAddr.c_str (), this->addr))
	throw string ("Invalid IPv6 address format '" + arg + "'");
    }		      
  catch (string e)
    {
      this->setErrMsg ("[IPv6Filter] " + e);
      return false;
    }


  this->any = false;
  return true;
}

void IPv6Filter::check (NDecResult &res)
{
  Record * rec = res.getRecord (DEC_IPV6);
  IPv6Hdr * hdr;
  u_int32_t * tgt = NULL;

  if (NULL == rec)
    return ;

  if (NULL == (hdr = static_cast<IPv6Hdr*> (rec->getPtr ())))
    return ;

  switch (this->type)
    {
    case FT_SRC_ADDR: tgt = hdr->ipSrc; break;
    case FT_DST_ADDR: tgt = hdr->ipDst; break;
    default:
      assert (0);
    }

  if (! this->any)
    {
      if (((tgt[0] & this->mask[0]) == this->addr[0]) && 
	  ((tgt[1] & this->mask[1]) == this->addr[1]) && 
	  ((tgt[2] & this->mask[2]) == this->addr[2]) && 
	  ((tgt[3] & this->mask[3]) == this->addr[3]))
	this->pushResult (tgt, IPv6Decoder::ADDR_LEN);
    }

  return ;
}
